feat:Text add htmlText

This commit is contained in:
pengfei.zhou 2020-04-14 11:44:22 +08:00 committed by osborn
parent e73ad1db2b
commit 9e0b3e067b
14 changed files with 437 additions and 1 deletions

View File

@ -16,6 +16,8 @@
package pub.doric.shader;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.text.Html;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.Gravity;
@ -26,6 +28,8 @@ import com.github.pengfeizhou.jscore.JSValue;
import pub.doric.DoricContext;
import pub.doric.extension.bridge.DoricPlugin;
import pub.doric.shader.richtext.CustomTagHandler;
import pub.doric.shader.richtext.HtmlParser;
import pub.doric.utils.DoricUtils;
/**
@ -153,6 +157,19 @@ public class TextNode extends ViewNode<TextView> {
view.getPaint().setUnderlineText(prop.asBoolean().value());
}
break;
case "htmlText":
if (prop.isString()) {
view.setText(
HtmlParser.buildSpannedText(prop.asString().value(),
new Html.ImageGetter() {
@Override
public Drawable getDrawable(String source) {
return null;
}
},
new CustomTagHandler()));
}
break;
default:
super.blend(view, name, prop);
break;

View File

@ -0,0 +1,115 @@
package pub.doric.shader.richtext;
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.text.Editable;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.AbsoluteSizeSpan;
import org.xml.sax.Attributes;
import java.util.Stack;
/**
* @Description: pub.doric.shader.richtext
* @Author: pengfei.zhou
* @CreateDate: 2020-04-14
*/
public class CustomTagHandler implements HtmlParser.TagHandler {
private Stack<Integer> startIndex = new Stack<>();
/**
* html attribute valuelike:<size value='16'></size>
*/
private Stack<String> propertyValue = new Stack<>();
@Override
public boolean handleTag(boolean opening, String tag, Editable output, Attributes attributes) {
if (opening) {
handleStartTag(tag, output, attributes);
} else {
handleEndTag(tag, output, attributes);
}
return false;
}
private void handleStartTag(String tag, Editable output, Attributes attributes) {
if (tag.equalsIgnoreCase("font")) {
handleStartFont(output, attributes);
}
}
private void handleEndTag(String tag, Editable output, Attributes attributes) {
if (tag.equalsIgnoreCase("font")) {
handleEndFont(output);
}
}
private void handleStartFont(Editable output, Attributes attributes) {
startIndex.push(output.length());
propertyValue.push(HtmlParser.getValue(attributes, "size"));
}
/**
* <font size='4'></font>
* * * 1-9
* * * 2-10
* * * 3-12
* * * 4-14
* * * 5-18
* * * 6-24
* * * 7-36
*/
private void handleEndFont(Editable output) {
String val = propertyValue.pop();
if (!TextUtils.isEmpty(val)) {
int value = 12;
try {
value = Integer.parseInt(val);
} catch (Exception e) {
e.printStackTrace();
}
switch (value) {
case 1:
value = 9;
break;
case 2:
value = 10;
break;
case 4:
value = 14;
break;
case 5:
value = 18;
break;
case 6:
value = 24;
break;
case 7:
value = 36;
break;
default:
value = 12;
break;
}
output.setSpan(new AbsoluteSizeSpan(value, true), startIndex.pop(), output.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}

View File

@ -0,0 +1,142 @@
package pub.doric.shader.richtext;
/*
* Copyright [2019] [Doric.Pub]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.text.Editable;
import android.text.Html;
import android.text.Spanned;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import java.util.ArrayDeque;
/**
* @Description: pub.doric.shader.richtext
* @Author: pengfei.zhou
* @CreateDate: 2020-04-14
*/
public class HtmlParser implements Html.TagHandler, ContentHandler {
//This approach has the advantage that it allows to disable processing of some tags while using default processing for others,
// e.g. you can make sure that ImageSpan objects are not created:
public interface TagHandler {
// return true here to indicate that this tag was handled and
// should not be processed further
boolean handleTag(boolean opening, String tag, Editable output, Attributes attributes);
}
public static Spanned buildSpannedText(String html, Html.ImageGetter imageGetter, TagHandler handler) {
return Html.fromHtml("<inject/>" + html, imageGetter, new HtmlParser(handler));
}
public static String getValue(Attributes attributes, String name) {
for (int i = 0, n = attributes.getLength(); i < n; i++) {
if (name.equals(attributes.getLocalName(i)))
return attributes.getValue(i);
}
return null;
}
private final TagHandler handler;
private ContentHandler mInnerContentHandler;
private Editable mEditable;
private ArrayDeque<Boolean> tagStatus = new ArrayDeque<>();
private HtmlParser(TagHandler handler) {
this.handler = handler;
}
@Override
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
if (mInnerContentHandler == null) {
// record result object
mEditable = output;
// record current content handler
mInnerContentHandler = xmlReader.getContentHandler();
// replace content handler with our own that forwards to calls to original when needed
xmlReader.setContentHandler(this);
// handle endElement() callback for <inject/> tag
tagStatus.addLast(Boolean.FALSE);
}
}
@Override
public void setDocumentLocator(Locator locator) {
mInnerContentHandler.setDocumentLocator(locator);
}
@Override
public void startDocument() throws SAXException {
mInnerContentHandler.startDocument();
}
@Override
public void endDocument() throws SAXException {
mInnerContentHandler.endDocument();
}
@Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
mInnerContentHandler.startPrefixMapping(prefix, uri);
}
@Override
public void endPrefixMapping(String prefix) throws SAXException {
mInnerContentHandler.endPrefixMapping(prefix);
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
boolean isHandled = handler.handleTag(true, localName, mEditable, attributes);
tagStatus.addLast(isHandled);
if (!isHandled) {
mInnerContentHandler.startElement(uri, localName, qName, attributes);
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (!tagStatus.removeLast()) {
mInnerContentHandler.endElement(uri, localName, qName);
}
handler.handleTag(false, localName, mEditable, null);
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
mInnerContentHandler.characters(ch, start, length);
}
@Override
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
mInnerContentHandler.ignorableWhitespace(ch, start, length);
}
@Override
public void processingInstruction(String target, String data) throws SAXException {
mInnerContentHandler.processingInstruction(target, data);
}
@Override
public void skippedEntity(String name) throws SAXException {
mInnerContentHandler.skippedEntity(name);
}
}

View File

@ -81,6 +81,131 @@ class TextDemo extends Panel {
textColor: Color.BLUE,
underline: true,
}),
text({
maxLines: 0,
maxWidth: root.width,
htmlText: `<div>
<h1>Supported tags by default</h1>
<ul>
<li>
<h2>br</h2>
<p>
To break<br/>lines<br/>in a<br/>paragraph,<br/>use the br tag.
</p>
</li>
<li>
<h2>p</h2>
<p>This is a paragraph.</p>
<p>This is a paragraph.</p>
<p>Paragraph elements are defined by p tags.</p>
<p style="color:#FF0000 text-decoration:line-through background:#eeeeee">
Support setting background color and foreground color and underline.</p>
</li>
<li>
<h2>ul</h2>
<p>An unordered list:</p>
<ul>
<li>coffee</li>
<li>tea</li>
<li>milk</li>
</ul>
</li>
<li>
<h2>div</h2>
<h3>This is a header</h3>
<p>This is a paragraph.</p>
<div style="color:#00FF00">
<h3>This is a header</h3>
<p>This is a paragraph.</p>
</div>
</li>
<li>
<h2>span</h2>
<p><span style="color:#FF0000">some text.</span>some other text.</p>
</li>
<li>
<h2>strong</h2>
<strong>This text is strong</strong>
</li>
<li>
<h2>b</h2>
<p>This is plain text <b>This is bold text</b></p>
</li>
<li>
<h2>em</h2>
<em>This text is emphasized</em>
</li>
<li>
<h2>cite</h2>
<cite>This text is cite</cite>
</li>
<li>
<h2>dfn</h2>
<dfn>This text is dfn</dfn>
</li>
<li>
<h2>i</h2>
<i>Italic</i>
</li>
<li>
<h2>big</h2>
<big>This text is big</big>
</li>
<li>
<h2>small</h2>
<small>This text is small</small>
</li>
<li>
<h2>font</h2>
<p><font color="red" size=30>This is some text!</font></p>
<p><font color="blue">This is some text!</font></p>
<p><font color="green">This is some text!</font></p>
</li>
<li>
<h1>blockquote</h1>
Here comes a long quotation:
<blockquote>
This is a long quotation. This is a long quotation. This is a long quotation. This
is a long quotation. This is a long quotation.
</blockquote>
Please note that the browser adds line breaks before and after the blockquote element and increases the margins.
</li>
<li>
<h1>tt</h1>
</li>
<li>
<h1>a</h1>
<a href="https://m.baidu.com">Click anchor</a>
</li>
<li>
<h1>u</h1>
<u>Underline</u>
</li>
<li>
<h1>strike,s,del</h1>
<strike>This text is strike</strike>
<s>This text is s</s>
<del>This text is del</del>
</li>
<li>h1-h6</li>
<h1>h1</h1>
<h2>h2</h2>
<h3>h3</h3>
<h4>h4</h4>
<h5>h5</h5>
<h6>h6</h6>
<li>
<h1>img</h1>
</li>
</ul>
</div>
`
}),
],
{
space: 10,

View File

@ -126,6 +126,12 @@ - (void)blendView:(UILabel *)view forPropName:(NSString *)name propValue:(id)pro
self.strikethrough = prop;
[self reloadParagraphStyle];
}];
} else if ([name isEqualToString:@"htmlText"]) {
NSAttributedString *attStr = [[NSAttributedString alloc] initWithData:[prop dataUsingEncoding:NSUnicodeStringEncoding]
options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType}
documentAttributes:nil
error:nil];
view.attributedText = attStr;
} else {
[super blendView:view forPropName:name propValue:prop];
}
@ -142,6 +148,9 @@ - (NSMutableParagraphStyle *)ensureParagraphStyle {
- (void)reloadParagraphStyle {
NSString *labelText = self.view.text ?: @"";
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:labelText];
if (self.view.attributedText) {
[attributedString setAttributedString:self.view.attributedText];
}
[attributedString addAttribute:NSParagraphStyleAttributeName value:self.paragraphStyle range:NSMakeRange(0, [labelText length])];
if (self.underline) {
[attributedString addAttribute:NSUnderlineStyleAttributeName value:self.underline range:NSMakeRange(0, [labelText length])];

View File

@ -1690,6 +1690,10 @@ var Text = /** @class */ (function (_super) {
Property,
__metadata$3("design:type", Boolean)
], Text.prototype, "underline", void 0);
__decorate$3([
Property,
__metadata$3("design:type", String)
], Text.prototype, "htmlText", void 0);
return Text;
}(View));
function text(config) {

View File

@ -1258,6 +1258,10 @@ __decorate$3([
Property,
__metadata$3("design:type", Boolean)
], Text.prototype, "underline", void 0);
__decorate$3([
Property,
__metadata$3("design:type", String)
], Text.prototype, "htmlText", void 0);
function text(config) {
const ret = new Text;
ret.layoutConfig = layoutConfig().fit();

View File

@ -2717,6 +2717,10 @@ __decorate$3([
Property,
__metadata$3("design:type", Boolean)
], Text.prototype, "underline", void 0);
__decorate$3([
Property,
__metadata$3("design:type", String)
], Text.prototype, "htmlText", void 0);
function text(config) {
const ret = new Text;
ret.layoutConfig = layoutConfig().fit();

2
doric-js/index.d.ts vendored
View File

@ -486,6 +486,7 @@ declare module 'doric/lib/src/widget/text' {
lineSpacing?: number;
strikethrough?: boolean;
underline?: boolean;
htmlText?: string;
}
export class Text extends View implements IText {
text?: string;
@ -500,6 +501,7 @@ declare module 'doric/lib/src/widget/text' {
lineSpacing?: number;
strikethrough?: boolean;
underline?: boolean;
htmlText?: string;
}
export function text(config: IText): Text;
}

View File

@ -14,6 +14,7 @@ export interface IText extends IView {
lineSpacing?: number;
strikethrough?: boolean;
underline?: boolean;
htmlText?: string;
}
export declare class Text extends View implements IText {
text?: string;
@ -28,5 +29,6 @@ export declare class Text extends View implements IText {
lineSpacing?: number;
strikethrough?: boolean;
underline?: boolean;
htmlText?: string;
}
export declare function text(config: IText): Text;

View File

@ -76,6 +76,10 @@ __decorate([
Property,
__metadata("design:type", Boolean)
], Text.prototype, "underline", void 0);
__decorate([
Property,
__metadata("design:type", String)
], Text.prototype, "htmlText", void 0);
export function text(config) {
const ret = new Text;
ret.layoutConfig = layoutConfig().fit();

View File

@ -31,6 +31,7 @@ export interface IText extends IView {
lineSpacing?: number
strikethrough?: boolean
underline?: boolean
htmlText?: string
}
export class Text extends View implements IText {
@ -69,6 +70,9 @@ export class Text extends View implements IText {
@Property
underline?: boolean
@Property
htmlText?: string
}
export function text(config: IText) {

View File

@ -2775,6 +2775,10 @@ __decorate$3([
Property,
__metadata$3("design:type", Boolean)
], Text.prototype, "underline", void 0);
__decorate$3([
Property,
__metadata$3("design:type", String)
], Text.prototype, "htmlText", void 0);
function text(config) {
const ret = new Text;
ret.layoutConfig = layoutConfig().fit();

File diff suppressed because one or more lines are too long