Showing
8 changed files
with
220 additions
and
17 deletions
| @@ -18,7 +18,7 @@ DART_BIN=$(FLUTTER)/bin/dart | @@ -18,7 +18,7 @@ DART_BIN=$(FLUTTER)/bin/dart | ||
| 18 | DART_SRC=$(shell find . -name '*.dart') | 18 | DART_SRC=$(shell find . -name '*.dart') |
| 19 | CLNG_SRC=$(shell find printing/ios printing/macos printing/windows printing/linux printing/android -name '*.cpp' -o -name '*.cc' -o -name '*.m' -o -name '*.h' -o -name '*.java') | 19 | CLNG_SRC=$(shell find printing/ios printing/macos printing/windows printing/linux printing/android -name '*.cpp' -o -name '*.cc' -o -name '*.m' -o -name '*.h' -o -name '*.java') |
| 20 | SWFT_SRC=$(shell find printing/ios printing/macos -name '*.swift') | 20 | SWFT_SRC=$(shell find printing/ios printing/macos -name '*.swift') |
| 21 | -FONTS=pdf/open-sans.ttf pdf/open-sans-bold.ttf pdf/roboto.ttf pdf/noto-sans.ttf pdf/genyomintw.ttf pdf/hacen-tunisia.ttf pdf/material.ttf | 21 | +FONTS=pdf/open-sans.ttf pdf/open-sans-bold.ttf pdf/roboto.ttf pdf/noto-sans.ttf pdf/genyomintw.ttf pdf/hacen-tunisia.ttf pdf/material.ttf pdf/emoji.ttf |
| 22 | COV_PORT=9292 | 22 | COV_PORT=9292 |
| 23 | SVG=blend_and_mask blend_mode_devil clip_path clip_path_2 clip_path_2 clip_path_3 clip_path_3 dash_path ellipse empty_defs equation fill-rule-inherit group_composite_opacity group_fill_opacity group_mask group_opacity group_opacity_transform hidden href-fill image image_def implicit_fill_with_opacity linear_gradient linear_gradient_2 linear_gradient_absolute_user_space_translate linear_gradient_percentage_bounding_translate linear_gradient_percentage_user_space_translate linear_gradient_xlink male mask mask_with_gradient mask_with_use mask_with_use2 nested_group opacity_on_path radial_gradient radial_gradient_absolute_user_space_translate radial_gradient_focal radial_gradient_percentage_bounding_translate radial_gradient_percentage_user_space_translate radial_gradient_xlink radial_ref_linear_gradient rect_rrect rect_rrect_no_ry stroke_inherit_circles style_attr text text_2 text_3 use_circles use_circles_def use_emc2 use_fill use_opacity_grid width_height_viewbox flutter_logo emoji_u1f600 text_transform dart new-pause-button new-send-circle new-gif new-camera new-image numeric_25 new-mention new-gif-button new-action-expander new-play-button aa alphachannel Ghostscript_Tiger Firefox_Logo_2017 chess_knight Flag_of_the_United_States | 23 | SVG=blend_and_mask blend_mode_devil clip_path clip_path_2 clip_path_2 clip_path_3 clip_path_3 dash_path ellipse empty_defs equation fill-rule-inherit group_composite_opacity group_fill_opacity group_mask group_opacity group_opacity_transform hidden href-fill image image_def implicit_fill_with_opacity linear_gradient linear_gradient_2 linear_gradient_absolute_user_space_translate linear_gradient_percentage_bounding_translate linear_gradient_percentage_user_space_translate linear_gradient_xlink male mask mask_with_gradient mask_with_use mask_with_use2 nested_group opacity_on_path radial_gradient radial_gradient_absolute_user_space_translate radial_gradient_focal radial_gradient_percentage_bounding_translate radial_gradient_percentage_user_space_translate radial_gradient_xlink radial_ref_linear_gradient rect_rrect rect_rrect_no_ry stroke_inherit_circles style_attr text text_2 text_3 use_circles use_circles_def use_emc2 use_fill use_opacity_grid width_height_viewbox flutter_logo emoji_u1f600 text_transform dart new-pause-button new-send-circle new-gif new-camera new-image numeric_25 new-mention new-gif-button new-action-expander new-play-button aa alphachannel Ghostscript_Tiger Firefox_Logo_2017 chess_knight Flag_of_the_United_States |
| 24 | 24 | ||
| @@ -42,6 +42,9 @@ pdf/genyomintw.ttf: | @@ -42,6 +42,9 @@ pdf/genyomintw.ttf: | ||
| 42 | pdf/material.ttf: | 42 | pdf/material.ttf: |
| 43 | curl -L "https://github.com/google/material-design-icons/raw/master/font/MaterialIcons-Regular.ttf" > $@ | 43 | curl -L "https://github.com/google/material-design-icons/raw/master/font/MaterialIcons-Regular.ttf" > $@ |
| 44 | 44 | ||
| 45 | +pdf/emoji.ttf: | ||
| 46 | + curl -L https://github.com/googlefonts/noto-emoji/raw/main/fonts/NotoColorEmoji.ttf > $@ | ||
| 47 | + | ||
| 45 | demo/assets/logo.svg: | 48 | demo/assets/logo.svg: |
| 46 | curl -L "http://pigment.github.io/fake-logos/logos/vector/color/auto-speed.svg" > $@ | 49 | curl -L "http://pigment.github.io/fake-logos/logos/vector/color/auto-speed.svg" > $@ |
| 47 | 50 |
| @@ -68,6 +68,52 @@ class TtfGlyphInfo { | @@ -68,6 +68,52 @@ class TtfGlyphInfo { | ||
| 68 | String toString() => 'Glyph $index $compounds'; | 68 | String toString() => 'Glyph $index $compounds'; |
| 69 | } | 69 | } |
| 70 | 70 | ||
| 71 | +class TtfBitmapInfo { | ||
| 72 | + const TtfBitmapInfo( | ||
| 73 | + this.data, | ||
| 74 | + this.height, | ||
| 75 | + this.width, | ||
| 76 | + this.horiBearingX, | ||
| 77 | + this.horiBearingY, | ||
| 78 | + this.horiAdvance, | ||
| 79 | + this.vertBearingX, | ||
| 80 | + this.vertBearingY, | ||
| 81 | + this.vertAdvance, | ||
| 82 | + this.ascent, | ||
| 83 | + this.descent, | ||
| 84 | + ); | ||
| 85 | + | ||
| 86 | + final Uint8List data; | ||
| 87 | + final int height; | ||
| 88 | + final int width; | ||
| 89 | + final int horiBearingX; | ||
| 90 | + final int horiBearingY; | ||
| 91 | + final int horiAdvance; | ||
| 92 | + final int vertBearingX; | ||
| 93 | + final int vertBearingY; | ||
| 94 | + final int vertAdvance; | ||
| 95 | + final int ascent; | ||
| 96 | + final int descent; | ||
| 97 | + | ||
| 98 | + PdfFontMetrics get metrics { | ||
| 99 | + final coef = 1.0 / height; | ||
| 100 | + return PdfFontMetrics( | ||
| 101 | + bottom: horiBearingY * coef, | ||
| 102 | + left: horiBearingX * coef, | ||
| 103 | + top: horiBearingY * coef - height * coef, | ||
| 104 | + right: horiAdvance * coef, | ||
| 105 | + ascent: ascent * coef, | ||
| 106 | + descent: horiBearingY * coef, | ||
| 107 | + advanceWidth: horiAdvance * coef, | ||
| 108 | + leftBearing: horiBearingX * coef, | ||
| 109 | + ); | ||
| 110 | + } | ||
| 111 | + | ||
| 112 | + @override | ||
| 113 | + String toString() => | ||
| 114 | + 'Bitmap Glyph ${width}x$height horiBearingX:$horiBearingX horiBearingY:$horiBearingY horiAdvance:$horiAdvance ascender:$ascent descender:$descent'; | ||
| 115 | +} | ||
| 116 | + | ||
| 71 | class TtfParser { | 117 | class TtfParser { |
| 72 | TtfParser(ByteData bytes) : bytes = UnmodifiableByteDataView(bytes) { | 118 | TtfParser(ByteData bytes) : bytes = UnmodifiableByteDataView(bytes) { |
| 73 | final numTables = bytes.getUint16(4); | 119 | final numTables = bytes.getUint16(4); |
| @@ -92,14 +138,17 @@ class TtfParser { | @@ -92,14 +138,17 @@ class TtfParser { | ||
| 92 | 'Unable to find the `cmap` table. This file is not a supported TTF font'); | 138 | 'Unable to find the `cmap` table. This file is not a supported TTF font'); |
| 93 | assert(tableOffsets.containsKey(maxp_table), | 139 | assert(tableOffsets.containsKey(maxp_table), |
| 94 | 'Unable to find the `maxp` table. This file is not a supported TTF font'); | 140 | 'Unable to find the `maxp` table. This file is not a supported TTF font'); |
| 95 | - assert(tableOffsets.containsKey(loca_table), | ||
| 96 | - 'Unable to find the `loca` table. This file is not a supported TTF font'); | ||
| 97 | - assert(tableOffsets.containsKey(glyf_table), | ||
| 98 | - 'Unable to find the `glyf` table. This file is not a supported TTF font'); | ||
| 99 | 141 | ||
| 100 | _parseCMap(); | 142 | _parseCMap(); |
| 101 | - _parseIndexes(); | ||
| 102 | - _parseGlyphs(); | 143 | + if (tableOffsets.containsKey(loca_table) && |
| 144 | + tableOffsets.containsKey(glyf_table)) { | ||
| 145 | + _parseIndexes(); | ||
| 146 | + _parseGlyphs(); | ||
| 147 | + } | ||
| 148 | + if (tableOffsets.containsKey(cblc_table) && | ||
| 149 | + tableOffsets.containsKey(cbdt_table)) { | ||
| 150 | + _parseBitmaps(); | ||
| 151 | + } | ||
| 103 | } | 152 | } |
| 104 | 153 | ||
| 105 | static const String head_table = 'head'; | 154 | static const String head_table = 'head'; |
| @@ -110,14 +159,17 @@ class TtfParser { | @@ -110,14 +159,17 @@ class TtfParser { | ||
| 110 | static const String maxp_table = 'maxp'; | 159 | static const String maxp_table = 'maxp'; |
| 111 | static const String loca_table = 'loca'; | 160 | static const String loca_table = 'loca'; |
| 112 | static const String glyf_table = 'glyf'; | 161 | static const String glyf_table = 'glyf'; |
| 162 | + static const String cblc_table = 'CBLC'; | ||
| 163 | + static const String cbdt_table = 'CBDT'; | ||
| 113 | 164 | ||
| 114 | final UnmodifiableByteDataView bytes; | 165 | final UnmodifiableByteDataView bytes; |
| 115 | - final Map<String, int> tableOffsets = <String, int>{}; | ||
| 116 | - final Map<String, int> tableSize = <String, int>{}; | 166 | + final tableOffsets = <String, int>{}; |
| 167 | + final tableSize = <String, int>{}; | ||
| 117 | 168 | ||
| 118 | - final Map<int, int> charToGlyphIndexMap = <int, int>{}; | ||
| 119 | - final List<int> glyphOffsets = <int>[]; | ||
| 120 | - final Map<int, PdfFontMetrics> glyphInfoMap = <int, PdfFontMetrics>{}; | 169 | + final charToGlyphIndexMap = <int, int>{}; |
| 170 | + final glyphOffsets = <int>[]; | ||
| 171 | + final glyphInfoMap = <int, PdfFontMetrics>{}; | ||
| 172 | + final bitmapOffsets = <int, TtfBitmapInfo>{}; | ||
| 121 | 173 | ||
| 122 | int get unitsPerEm => bytes.getUint16(tableOffsets[head_table]! + 18); | 174 | int get unitsPerEm => bytes.getUint16(tableOffsets[head_table]! + 18); |
| 123 | 175 | ||
| @@ -145,9 +197,14 @@ class TtfParser { | @@ -145,9 +197,14 @@ class TtfParser { | ||
| 145 | 197 | ||
| 146 | bool get unicode => bytes.getUint32(0) == 0x10000; | 198 | bool get unicode => bytes.getUint32(0) == 0x10000; |
| 147 | 199 | ||
| 200 | + bool get isBitmap => bitmapOffsets.isNotEmpty && glyphOffsets.isEmpty; | ||
| 201 | + | ||
| 148 | // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html | 202 | // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html |
| 149 | String? getNameID(TtfParserName fontNameID) { | 203 | String? getNameID(TtfParserName fontNameID) { |
| 150 | - final basePosition = tableOffsets[name_table]!; | 204 | + final basePosition = tableOffsets[name_table]; |
| 205 | + if (basePosition == null) { | ||
| 206 | + return null; | ||
| 207 | + } | ||
| 151 | // final format = bytes.getUint16(basePosition); | 208 | // final format = bytes.getUint16(basePosition); |
| 152 | final count = bytes.getUint16(basePosition + 2); | 209 | final count = bytes.getUint16(basePosition + 2); |
| 153 | final stringOffset = bytes.getUint16(basePosition + 4); | 210 | final stringOffset = bytes.getUint16(basePosition + 4); |
| @@ -288,15 +345,15 @@ class TtfParser { | @@ -288,15 +345,15 @@ class TtfParser { | ||
| 288 | } | 345 | } |
| 289 | 346 | ||
| 290 | void _parseIndexes() { | 347 | void _parseIndexes() { |
| 291 | - final basePosition = tableOffsets[loca_table]; | 348 | + final basePosition = tableOffsets[loca_table]!; |
| 292 | final numGlyphs = this.numGlyphs; | 349 | final numGlyphs = this.numGlyphs; |
| 293 | if (indexToLocFormat == 0) { | 350 | if (indexToLocFormat == 0) { |
| 294 | for (var i = 0; i < numGlyphs; i++) { | 351 | for (var i = 0; i < numGlyphs; i++) { |
| 295 | - glyphOffsets.add(bytes.getUint16(basePosition! + i * 2) * 2); | 352 | + glyphOffsets.add(bytes.getUint16(basePosition + i * 2) * 2); |
| 296 | } | 353 | } |
| 297 | } else { | 354 | } else { |
| 298 | for (var i = 0; i < numGlyphs; i++) { | 355 | for (var i = 0; i < numGlyphs; i++) { |
| 299 | - glyphOffsets.add(bytes.getUint32(basePosition! + i * 4)); | 356 | + glyphOffsets.add(bytes.getUint32(basePosition + i * 4)); |
| 300 | } | 357 | } |
| 301 | } | 358 | } |
| 302 | } | 359 | } |
| @@ -465,4 +522,92 @@ class TtfParser { | @@ -465,4 +522,92 @@ class TtfParser { | ||
| 465 | } | 522 | } |
| 466 | return String.fromCharCodes(charCodes); | 523 | return String.fromCharCodes(charCodes); |
| 467 | } | 524 | } |
| 525 | + | ||
| 526 | + // https://docs.microsoft.com/en-us/typography/opentype/spec/ebdt | ||
| 527 | + void _parseBitmaps() { | ||
| 528 | + final baseOffset = tableOffsets[cblc_table]!; | ||
| 529 | + final pngOffset = tableOffsets[cbdt_table]!; | ||
| 530 | + | ||
| 531 | + // CBLC Header | ||
| 532 | + final numSizes = bytes.getUint32(baseOffset + 4); | ||
| 533 | + var bitmapSize = baseOffset + 8; | ||
| 534 | + | ||
| 535 | + for (var bitmapSizeIndex = 0; | ||
| 536 | + bitmapSizeIndex < numSizes; | ||
| 537 | + bitmapSizeIndex++) { | ||
| 538 | + // BitmapSize Record | ||
| 539 | + final indexSubTableArrayOffset = baseOffset + bytes.getUint32(bitmapSize); | ||
| 540 | + // final indexTablesSize = bytes.getUint32(bitmapSize + 4); | ||
| 541 | + final numberOfIndexSubTables = bytes.getUint32(bitmapSize + 8); | ||
| 542 | + | ||
| 543 | + final ascender = bytes.getInt8(bitmapSize + 12); | ||
| 544 | + final descender = bytes.getInt8(bitmapSize + 13); | ||
| 545 | + | ||
| 546 | + // final startGlyphIndex = bytes.getUint16(bitmapSize + 16 + 12 * 2); | ||
| 547 | + // final endGlyphIndex = bytes.getUint16(bitmapSize + 16 + 12 * 2 + 2); | ||
| 548 | + // final ppemX = bytes.getUint8(bitmapSize + 16 + 12 * 2 + 4); | ||
| 549 | + // final ppemY = bytes.getUint8(bitmapSize + 16 + 12 * 2 + 5); | ||
| 550 | + // final bitDepth = bytes.getUint8(bitmapSize + 16 + 12 * 2 + 6); | ||
| 551 | + // final flags = bytes.getUint8(bitmapSize + 16 + 12 * 2 + 7); | ||
| 552 | + | ||
| 553 | + var subTableArrayOffset = indexSubTableArrayOffset; | ||
| 554 | + for (var indexSubTable = 0; | ||
| 555 | + indexSubTable < numberOfIndexSubTables; | ||
| 556 | + indexSubTable++) { | ||
| 557 | + // IndexSubTableArray | ||
| 558 | + final firstGlyphIndex = bytes.getUint16(subTableArrayOffset); | ||
| 559 | + final lastGlyphIndex = bytes.getUint16(subTableArrayOffset + 2); | ||
| 560 | + final additionalOffsetToIndexSubtable = | ||
| 561 | + indexSubTableArrayOffset + bytes.getUint32(subTableArrayOffset + 4); | ||
| 562 | + | ||
| 563 | + // IndexSubHeader | ||
| 564 | + final indexFormat = bytes.getUint16(additionalOffsetToIndexSubtable); | ||
| 565 | + final imageFormat = | ||
| 566 | + bytes.getUint16(additionalOffsetToIndexSubtable + 2); | ||
| 567 | + final imageDataOffset = | ||
| 568 | + pngOffset + bytes.getUint32(additionalOffsetToIndexSubtable + 4); | ||
| 569 | + | ||
| 570 | + if (indexFormat == 1) { | ||
| 571 | + // IndexSubTable1 | ||
| 572 | + | ||
| 573 | + for (var glyph = firstGlyphIndex; glyph <= lastGlyphIndex; glyph++) { | ||
| 574 | + final sbitOffset = imageDataOffset + | ||
| 575 | + bytes.getUint32(additionalOffsetToIndexSubtable + | ||
| 576 | + (glyph - firstGlyphIndex + 2) * 4); | ||
| 577 | + | ||
| 578 | + if (imageFormat == 17) { | ||
| 579 | + final height = bytes.getUint8(sbitOffset); | ||
| 580 | + final width = bytes.getUint8(sbitOffset + 1); | ||
| 581 | + final bearingX = bytes.getInt8(sbitOffset + 2); | ||
| 582 | + final bearingY = bytes.getInt8(sbitOffset + 3); | ||
| 583 | + final advance = bytes.getUint8(sbitOffset + 4); | ||
| 584 | + final dataLen = bytes.getUint32(sbitOffset + 5); | ||
| 585 | + | ||
| 586 | + bitmapOffsets[glyph] = TtfBitmapInfo( | ||
| 587 | + bytes.buffer.asUint8List( | ||
| 588 | + bytes.offsetInBytes + sbitOffset + 9, | ||
| 589 | + dataLen, | ||
| 590 | + ), | ||
| 591 | + height, | ||
| 592 | + width, | ||
| 593 | + bearingX, | ||
| 594 | + bearingY, | ||
| 595 | + advance, | ||
| 596 | + 0, | ||
| 597 | + 0, | ||
| 598 | + 0, | ||
| 599 | + ascender, | ||
| 600 | + descender); | ||
| 601 | + } | ||
| 602 | + } | ||
| 603 | + } | ||
| 604 | + | ||
| 605 | + subTableArrayOffset += 8; | ||
| 606 | + } | ||
| 607 | + bitmapSize += 16 + 12 * 2 + 8; | ||
| 608 | + } | ||
| 609 | + } | ||
| 610 | + | ||
| 611 | + TtfBitmapInfo? getBitmap(int charcode) => | ||
| 612 | + bitmapOffsets[charToGlyphIndexMap[charcode]]; | ||
| 468 | } | 613 | } |
| @@ -23,7 +23,7 @@ class PdfUnicodeCmap extends PdfObjectStream { | @@ -23,7 +23,7 @@ class PdfUnicodeCmap extends PdfObjectStream { | ||
| 23 | PdfUnicodeCmap(PdfDocument pdfDocument, this.protect) : super(pdfDocument); | 23 | PdfUnicodeCmap(PdfDocument pdfDocument, this.protect) : super(pdfDocument); |
| 24 | 24 | ||
| 25 | /// List of characters | 25 | /// List of characters |
| 26 | - final List<int> cmap = <int>[0]; | 26 | + final cmap = <int>[]; |
| 27 | 27 | ||
| 28 | /// Protects the text from being "seen" by the PDF reader. | 28 | /// Protects the text from being "seen" by the PDF reader. |
| 29 | final bool protect; | 29 | final bool protect; |
| @@ -24,6 +24,8 @@ import 'annotations.dart'; | @@ -24,6 +24,8 @@ import 'annotations.dart'; | ||
| 24 | import 'basic.dart'; | 24 | import 'basic.dart'; |
| 25 | import 'document.dart'; | 25 | import 'document.dart'; |
| 26 | import 'geometry.dart'; | 26 | import 'geometry.dart'; |
| 27 | +import 'image.dart'; | ||
| 28 | +import 'image_provider.dart'; | ||
| 27 | import 'multi_page.dart'; | 29 | import 'multi_page.dart'; |
| 28 | import 'placeholders.dart'; | 30 | import 'placeholders.dart'; |
| 29 | import 'text_style.dart'; | 31 | import 'text_style.dart'; |
| @@ -690,6 +692,25 @@ class RichText extends Widget with SpanningWidget { | @@ -690,6 +692,25 @@ class RichText extends Widget with SpanningWidget { | ||
| 690 | _decorations.add(td); | 692 | _decorations.add(td); |
| 691 | } | 693 | } |
| 692 | 694 | ||
| 695 | + InlineSpan _addEmoji({ | ||
| 696 | + required TtfBitmapInfo bitmap, | ||
| 697 | + double baseline = 0, | ||
| 698 | + required TextStyle style, | ||
| 699 | + AnnotationBuilder? annotation, | ||
| 700 | + }) { | ||
| 701 | + final metrics = bitmap.metrics * style.fontSize!; | ||
| 702 | + | ||
| 703 | + return WidgetSpan( | ||
| 704 | + child: SizedBox( | ||
| 705 | + height: style.fontSize, | ||
| 706 | + child: Image(MemoryImage(bitmap.data)), | ||
| 707 | + ), | ||
| 708 | + style: style, | ||
| 709 | + baseline: baseline + metrics.ascent + metrics.descent - metrics.height, | ||
| 710 | + annotation: annotation, | ||
| 711 | + ); | ||
| 712 | + } | ||
| 713 | + | ||
| 693 | InlineSpan _addText({ | 714 | InlineSpan _addText({ |
| 694 | required List<int> text, | 715 | required List<int> text, |
| 695 | int start = 0, | 716 | int start = 0, |
| @@ -770,6 +791,19 @@ class RichText extends Widget with SpanningWidget { | @@ -770,6 +791,19 @@ class RichText extends Widget with SpanningWidget { | ||
| 770 | for (final fb in style.fontFallback) { | 791 | for (final fb in style.fontFallback) { |
| 771 | final font = fb.getFont(context); | 792 | final font = fb.getFont(context); |
| 772 | if (font.isRuneSupported(rune)) { | 793 | if (font.isRuneSupported(rune)) { |
| 794 | + if (font is PdfTtfFont) { | ||
| 795 | + final bitmap = font.font.getBitmap(rune); | ||
| 796 | + if (bitmap != null) { | ||
| 797 | + spans.add(_addEmoji( | ||
| 798 | + bitmap: bitmap, | ||
| 799 | + style: style, | ||
| 800 | + baseline: span.baseline, | ||
| 801 | + annotation: annotation, | ||
| 802 | + )); | ||
| 803 | + found = true; | ||
| 804 | + break; | ||
| 805 | + } | ||
| 806 | + } | ||
| 773 | spans.add(_addText( | 807 | spans.add(_addText( |
| 774 | text: [rune], | 808 | text: [rune], |
| 775 | style: style.copyWith( | 809 | style: style.copyWith( |
| @@ -27,6 +27,7 @@ late Document pdf; | @@ -27,6 +27,7 @@ late Document pdf; | ||
| 27 | late Font ttf; | 27 | late Font ttf; |
| 28 | late Font ttfBold; | 28 | late Font ttfBold; |
| 29 | late Font asian; | 29 | late Font asian; |
| 30 | +late Font emoji; | ||
| 30 | 31 | ||
| 31 | Iterable<TextDecoration> permute( | 32 | Iterable<TextDecoration> permute( |
| 32 | List<TextDecoration> prefix, List<TextDecoration> remaining) sync* { | 33 | List<TextDecoration> prefix, List<TextDecoration> remaining) sync* { |
| @@ -48,6 +49,7 @@ void main() { | @@ -48,6 +49,7 @@ void main() { | ||
| 48 | ttf = loadFont('open-sans.ttf'); | 49 | ttf = loadFont('open-sans.ttf'); |
| 49 | ttfBold = loadFont('open-sans-bold.ttf'); | 50 | ttfBold = loadFont('open-sans-bold.ttf'); |
| 50 | asian = loadFont('genyomintw.ttf'); | 51 | asian = loadFont('genyomintw.ttf'); |
| 52 | + emoji = loadFont('emoji.ttf'); | ||
| 51 | pdf = Document(); | 53 | pdf = Document(); |
| 52 | }); | 54 | }); |
| 53 | 55 | ||
| @@ -357,6 +359,20 @@ void main() { | @@ -357,6 +359,20 @@ void main() { | ||
| 357 | ); | 359 | ); |
| 358 | }); | 360 | }); |
| 359 | 361 | ||
| 362 | + test('Text Widgets Emojis', () { | ||
| 363 | + pdf.addPage( | ||
| 364 | + Page( | ||
| 365 | + build: (Context context) => Text( | ||
| 366 | + 'Hello 🐈! Dancing 💃🏃', | ||
| 367 | + style: TextStyle( | ||
| 368 | + fontSize: 30, | ||
| 369 | + fontFallback: [emoji], | ||
| 370 | + ), | ||
| 371 | + ), | ||
| 372 | + ), | ||
| 373 | + ); | ||
| 374 | + }); | ||
| 375 | + | ||
| 360 | tearDownAll(() async { | 376 | tearDownAll(() async { |
| 361 | final file = File('widgets-text.pdf'); | 377 | final file = File('widgets-text.pdf'); |
| 362 | await file.writeAsBytes(await pdf.save()); | 378 | await file.writeAsBytes(await pdf.save()); |
| @@ -128,8 +128,12 @@ void main(List<String> args) async { | @@ -128,8 +128,12 @@ void main(List<String> args) async { | ||
| 128 | } | 128 | } |
| 129 | 129 | ||
| 130 | for (final entry in <String, String>{ | 130 | for (final entry in <String, String>{ |
| 131 | + 'CupertinoIcons': | ||
| 132 | + 'https://github.com/flutter/packages/blob/master/third_party/packages/cupertino_icons/assets/CupertinoIcons.ttf', | ||
| 131 | 'MaterialIcons': | 133 | 'MaterialIcons': |
| 132 | 'https://fonts.gstatic.com/s/materialicons/v98/flUhRq6tzZclQEJ-Vdg-IuiaDsNZ.ttf', | 134 | 'https://fonts.gstatic.com/s/materialicons/v98/flUhRq6tzZclQEJ-Vdg-IuiaDsNZ.ttf', |
| 135 | + 'NotoColorEmoji': | ||
| 136 | + 'https://github.com/googlefonts/noto-emoji/raw/main/fonts/NotoColorEmoji.ttf', | ||
| 133 | }.entries) { | 137 | }.entries) { |
| 134 | output.writeln(''); | 138 | output.writeln(''); |
| 135 | output.writeln('/// ${entry.key}'); | 139 | output.writeln('/// ${entry.key}'); |
No preview for this file type
-
Please register or login to post a comment