David PHAM-VAN

Add Unicode support

... ... @@ -15,7 +15,7 @@
DART_SRC=$(shell find . -name '*.dart')
CLNG_SRC=$(shell find printing/ios -name '*.java' -o -name '*.m' -o -name '*.h') $(shell find printing/android -name '*.java' -o -name '*.m' -o -name '*.h')
SWFT_SRC=$(shell find . -name '*.swift')
FONTS=pdf/open-sans.ttf pdf/roboto.ttf
FONTS=pdf/open-sans.ttf pdf/roboto.ttf pdf/noto-sans.ttf
all: $(FONTS) format
... ... @@ -25,6 +25,9 @@ pdf/open-sans.ttf:
pdf/roboto.ttf:
curl -L "https://github.com/google/fonts/raw/master/apache/robotomono/RobotoMono-Regular.ttf" > $@
pdf/noto-sans.ttf:
curl -L "https://raw.githubusercontent.com/google/fonts/master/ofl/notosans/NotoSans-Regular.ttf" > $@
format: format-dart format-clang format-swift
format-dart: $(DART_SRC)
... ...
# 1.3.5
* Add some color functions
* Remove color constants from PdfColor, use PdfColors
* Add TTF Font SubSetting
* Add Unicode support for TTF Fonts
# 1.3.4
* Add available dimensions for PdfPageFormat
... ...
... ... @@ -54,8 +54,10 @@ part 'src/polygon.dart';
part 'src/rect.dart';
part 'src/stream.dart';
part 'src/ttf_parser.dart';
part 'src/ttf_writer.dart';
part 'src/ttffont.dart';
part 'src/type1_font.dart';
part 'src/type1_fonts.dart';
part 'src/unicode_cmap.dart';
part 'src/xobject.dart';
part 'src/xref.dart';
... ...
... ... @@ -153,4 +153,11 @@ abstract class PdfFont extends PdfObject {
@override
String toString() => 'Font($fontName)';
PdfStream putText(String text) {
return PdfStream()
..putBytes(latin1.encode('('))
..putTextBytes(latin1.encode(text))
..putBytes(latin1.encode(')'));
}
}
... ...
... ... @@ -30,7 +30,7 @@ class PdfFontDescriptor extends PdfObject {
params['/FontName'] = PdfStream.string('/' + ttfFont.fontName);
params['/FontFile2'] = file.ref();
params['/Flags'] = PdfStream.intNum(32);
params['/Flags'] = PdfStream.intNum(ttfFont.font.unicode ? 4 : 32);
params['/FontBBox'] = PdfStream()
..putIntArray(<int>[
(ttfFont.font.xMin / ttfFont.font.unitsPerEm * 1000).toInt(),
... ...
... ... @@ -195,7 +195,7 @@ class PdfGraphics {
buf.putString(' Td ${font.name} ');
buf.putNum(size);
buf.putString(' Tf ');
buf.putText(s);
buf.putStream(font.putText(s));
buf.putString(' Tj ET\n');
}
... ...
... ... @@ -16,6 +16,17 @@
part of pdf;
class TtfGlyphInfo {
TtfGlyphInfo(this.index, this.data, this.compounds);
final int index;
final Uint8List data;
final List<int> compounds;
@override
String toString() => 'Glyph $index $compounds';
}
class TtfParser {
TtfParser(this.bytes) {
final int numTables = bytes.getUint16(4);
... ... @@ -23,56 +34,61 @@ class TtfParser {
for (int i = 0; i < numTables; i++) {
final String name = utf8.decode(bytes.buffer.asUint8List(i * 16 + 12, 4));
final int offset = bytes.getUint32(i * 16 + 20);
_tableOffsets[name] = offset;
final int size = bytes.getUint32(i * 16 + 24);
tableOffsets[name] = offset;
tableSize[name] = size;
}
_parseFontName();
_parseCMap();
_parseIndexes();
_parseGlyf();
_parseGlyphs();
}
static const String _HEAD = 'head';
static const String _NAME = 'name';
static const String _HMTX = 'hmtx';
static const String _HHEA = 'hhea';
static const String _CMAP = 'cmap';
static const String _MAXP = 'maxp';
static const String _LOCA = 'loca';
static const String _GLYF = 'glyf';
static const String head_table = 'head';
static const String name_table = 'name';
static const String hmtx_table = 'hmtx';
static const String hhea_table = 'hhea';
static const String cmap_table = 'cmap';
static const String maxp_table = 'maxp';
static const String loca_table = 'loca';
static const String glyf_table = 'glyf';
final ByteData bytes;
final Map<String, int> _tableOffsets = <String, int>{};
final Map<String, int> tableOffsets = <String, int>{};
final Map<String, int> tableSize = <String, int>{};
String _fontName;
final Map<int, int> charToGlyphIndexMap = <int, int>{};
final List<int> glyphOffsets = <int>[];
final Map<int, PdfFontMetrics> glyphInfoMap = <int, PdfFontMetrics>{};
int get unitsPerEm => bytes.getUint16(_tableOffsets[_HEAD] + 18);
int get unitsPerEm => bytes.getUint16(tableOffsets[head_table] + 18);
int get xMin => bytes.getInt16(_tableOffsets[_HEAD] + 36);
int get xMin => bytes.getInt16(tableOffsets[head_table] + 36);
int get yMin => bytes.getInt16(_tableOffsets[_HEAD] + 38);
int get yMin => bytes.getInt16(tableOffsets[head_table] + 38);
int get xMax => bytes.getInt16(_tableOffsets[_HEAD] + 40);
int get xMax => bytes.getInt16(tableOffsets[head_table] + 40);
int get yMax => bytes.getInt16(_tableOffsets[_HEAD] + 42);
int get yMax => bytes.getInt16(tableOffsets[head_table] + 42);
int get indexToLocFormat => bytes.getInt16(_tableOffsets[_HEAD] + 50);
int get indexToLocFormat => bytes.getInt16(tableOffsets[head_table] + 50);
int get ascent => bytes.getInt16(_tableOffsets[_HHEA] + 4);
int get ascent => bytes.getInt16(tableOffsets[hhea_table] + 4);
int get descent => bytes.getInt16(_tableOffsets[_HHEA] + 6);
int get descent => bytes.getInt16(tableOffsets[hhea_table] + 6);
int get numOfLongHorMetrics => bytes.getInt16(_tableOffsets[_HHEA] + 34);
int get numOfLongHorMetrics => bytes.getInt16(tableOffsets[hhea_table] + 34);
int get numGlyphs => bytes.getInt16(_tableOffsets[_MAXP] + 4);
int get numGlyphs => bytes.getInt16(tableOffsets[maxp_table] + 4);
String get fontName => _fontName;
bool get unicode => bytes.getUint32(0) == 0x10000;
// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6name.html
void _parseFontName() {
final int basePosition = _tableOffsets[_NAME];
final int basePosition = tableOffsets[name_table];
final int count = bytes.getUint16(basePosition + 2);
final int stringOffset = bytes.getUint16(basePosition + 4);
int pos = basePosition + 6;
... ... @@ -105,7 +121,7 @@ class TtfParser {
}
void _parseCMap() {
final int basePosition = _tableOffsets[_CMAP];
final int basePosition = tableOffsets[cmap_table];
final int numSubTables = bytes.getUint16(basePosition + 2);
for (int i = 0; i < numSubTables; i++) {
final int offset = bytes.getUint32(basePosition + i * 8 + 8);
... ... @@ -190,7 +206,7 @@ class TtfParser {
}
void _parseIndexes() {
final int basePosition = _tableOffsets[_LOCA];
final int basePosition = tableOffsets[loca_table];
final int numGlyphs = this.numGlyphs;
if (indexToLocFormat == 0) {
for (int i = 0; i < numGlyphs; i++) {
... ... @@ -204,9 +220,9 @@ class TtfParser {
}
/// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6glyf.html
void _parseGlyf() {
final int baseOffset = _tableOffsets[_GLYF];
final int hmtxOffset = _tableOffsets[_HMTX];
void _parseGlyphs() {
final int baseOffset = tableOffsets[glyf_table];
final int hmtxOffset = tableOffsets[hmtx_table];
final int unitsPerEm = this.unitsPerEm;
int glyphIndex = 0;
for (int offset in glyphOffsets) {
... ... @@ -228,4 +244,117 @@ class TtfParser {
glyphIndex++;
}
}
/// http://stevehanov.ca/blog/?id=143
TtfGlyphInfo readGlyph(int index) {
assert(index != null);
assert(index < glyphOffsets.length);
final int start = tableOffsets[glyf_table] + glyphOffsets[index];
final int numberOfContours = bytes.getInt16(start);
assert(numberOfContours >= -1);
if (numberOfContours == -1) {
return _readCompoundGlyph(index, start, start + 10);
} else {
return _readSimpleGlyph(index, start, start + 10, numberOfContours);
}
}
TtfGlyphInfo _readSimpleGlyph(
int glyph, int start, int offset, int numberOfContours) {
const int X_IS_BYTE = 2;
const int Y_IS_BYTE = 4;
const int REPEAT = 8;
const int X_DELTA = 16;
const int Y_DELTA = 32;
int numPoints = 1;
for (int i = 0; i < numberOfContours; i++) {
numPoints = math.max(numPoints, bytes.getUint16(offset) + 1);
offset += 2;
}
// skip over intructions
offset += bytes.getUint16(offset) + 2;
if (numberOfContours == 0) {
return TtfGlyphInfo(
glyph,
Uint8List.view(bytes.buffer, start, offset - start),
<int>[],
);
}
final List<int> flags = <int>[];
for (int i = 0; i < numPoints; i++) {
final int flag = bytes.getUint8(offset++);
flags.add(flag);
if (flag & REPEAT != 0) {
int repeatCount = bytes.getUint8(offset++);
assert(repeatCount > 0);
i += repeatCount;
while (repeatCount-- > 0) {
flags.add(flag);
}
}
}
int byteFlag = X_IS_BYTE;
int deltaFlag = X_DELTA;
for (int a = 0; a < 2; a++) {
for (int i = 0; i < numPoints; i++) {
final int flag = flags[i];
if (flag & byteFlag != 0) {
offset++;
} else if (~flag & deltaFlag != 0) {
offset += 2;
}
}
byteFlag = Y_IS_BYTE;
deltaFlag = Y_DELTA;
}
return TtfGlyphInfo(
glyph,
Uint8List.view(bytes.buffer, start, offset - start),
<int>[],
);
}
TtfGlyphInfo _readCompoundGlyph(int glyph, int start, int offset) {
const int ARG_1_AND_2_ARE_WORDS = 1;
const int MORE_COMPONENTS = 32;
const int WE_HAVE_INSTRUCTIONS = 256;
final List<int> components = <int>[];
bool hasInstructions = false;
int flags = MORE_COMPONENTS;
while (flags & MORE_COMPONENTS != 0) {
flags = bytes.getUint16(offset);
final int glyphIndex = bytes.getUint16(offset + 2);
offset += (flags & ARG_1_AND_2_ARE_WORDS != 0) ? 8 : 6;
components.add(glyphIndex);
if (flags & WE_HAVE_INSTRUCTIONS != 0) {
assert(!hasInstructions); // Not already set
hasInstructions = true;
}
}
if (hasInstructions) {
offset += bytes.getUint16(offset) + 2;
}
return TtfGlyphInfo(
glyph,
Uint8List.view(bytes.buffer, start, offset - start),
components,
);
}
}
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* 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.
*/
part of pdf;
/// https://opentype.js.org/
class TtfWriter {
TtfWriter(this.ttf);
final TtfParser ttf;
int _calcTableChecksum(ByteData table) {
assert(table.lengthInBytes % 4 == 0);
int sum = 0;
for (int i = 0; i < table.lengthInBytes - 3; i += 4) {
sum = (sum + table.getUint32(i)) & 0xffffffff;
}
return sum;
}
void _updateCompoundGlyph(TtfGlyphInfo glyph, Map<int, int> compoundMap) {
const int ARG_1_AND_2_ARE_WORDS = 1;
const int MORE_COMPONENTS = 32;
int offset = 10;
final ByteData bytes = glyph.data.buffer
.asByteData(glyph.data.offsetInBytes, glyph.data.lengthInBytes);
int flags = MORE_COMPONENTS;
while (flags & MORE_COMPONENTS != 0) {
flags = bytes.getUint16(offset);
final int glyphIndex = bytes.getUint16(offset + 2);
bytes.setUint16(offset + 2, compoundMap[glyphIndex]);
offset += (flags & ARG_1_AND_2_ARE_WORDS != 0) ? 8 : 6;
}
}
int _wordAlign(int offset, [int align = 2]) {
return offset + ((align - (offset % align)) % align);
}
Uint8List withChars(List<int> chars) {
final Map<String, Uint8List> tables = <String, Uint8List>{};
final Map<String, int> tablesLength = <String, int>{};
// Create the glyphs table
final List<TtfGlyphInfo> glyphsInfo = <TtfGlyphInfo>[];
final Map<int, int> compounds = <int, int>{};
for (int index = 0; index < chars.length; index++) {
if (chars[index] == 32) {
final TtfGlyphInfo glyph = TtfGlyphInfo(32, Uint8List(0), <int>[]);
glyphsInfo.add(glyph);
continue;
}
final TtfGlyphInfo glyph = ttf.readGlyph(
chars[index] == 0 ? 0 : ttf.charToGlyphIndexMap[chars[index]]);
for (int g in glyph.compounds) {
compounds[g] = null;
}
glyphsInfo.add(glyph);
}
// Add compound glyphs
for (int compound in compounds.keys) {
final int index = chars.indexOf(compound);
if (index >= 0) {
compounds[compound] = index;
} else {
compounds[compound] = glyphsInfo.length;
final TtfGlyphInfo glyph = ttf.readGlyph(compound);
assert(glyph.compounds.isEmpty); // This is a simple glyph
glyphsInfo.add(glyph);
}
}
// Add one last empty glyph
final TtfGlyphInfo glyph = TtfGlyphInfo(32, Uint8List(0), <int>[]);
glyphsInfo.add(glyph);
// update compound indices
for (TtfGlyphInfo glyph in glyphsInfo) {
if (glyph.compounds.isNotEmpty) {
_updateCompoundGlyph(glyph, compounds);
}
}
int glyphsTableLength = 0;
for (TtfGlyphInfo glyph in glyphsInfo) {
glyphsTableLength =
_wordAlign(glyphsTableLength + glyph.data.lengthInBytes);
}
int offset = 0;
final Uint8List glyphsTable = Uint8List(_wordAlign(glyphsTableLength, 4));
tables[TtfParser.glyf_table] = glyphsTable;
tablesLength[TtfParser.glyf_table] = glyphsTableLength;
// Loca
if (ttf.indexToLocFormat == 0) {
tables[TtfParser.loca_table] =
Uint8List(_wordAlign(glyphsInfo.length * 2, 4)); // uint16
tablesLength[TtfParser.loca_table] = glyphsInfo.length * 2;
} else {
tables[TtfParser.loca_table] =
Uint8List(_wordAlign(glyphsInfo.length * 4, 4)); // uint32
tablesLength[TtfParser.loca_table] = glyphsInfo.length * 4;
}
{
final ByteData loca = tables[TtfParser.loca_table].buffer.asByteData();
int index = 0;
for (TtfGlyphInfo glyph in glyphsInfo) {
if (ttf.indexToLocFormat == 0) {
loca.setUint16(index, offset ~/ 2);
index += 2;
} else {
loca.setUint32(index, offset);
index += 4;
}
glyphsTable.setAll(offset, glyph.data);
offset = _wordAlign(offset + glyph.data.lengthInBytes);
}
}
{
// Head table
final int start = ttf.tableOffsets[TtfParser.head_table];
final int len = ttf.tableSize[TtfParser.head_table];
final Uint8List head = Uint8List.fromList(
ttf.bytes.buffer.asUint8List(start, _wordAlign(len, 4)));
head.buffer.asByteData().setUint32(8, 0); // checkSumAdjustment
tables[TtfParser.head_table] = head;
tablesLength[TtfParser.head_table] = len;
}
{
// MaxP table
final int start = ttf.tableOffsets[TtfParser.maxp_table];
final int len = ttf.tableSize[TtfParser.maxp_table];
final Uint8List maxp = Uint8List.fromList(
ttf.bytes.buffer.asUint8List(start, _wordAlign(len, 4)));
maxp.buffer.asByteData().setUint16(4, glyphsInfo.length);
tables[TtfParser.maxp_table] = maxp;
tablesLength[TtfParser.maxp_table] = len;
}
{
// HHEA table
final int start = ttf.tableOffsets[TtfParser.hhea_table];
final int len = ttf.tableSize[TtfParser.hhea_table];
final Uint8List hhea = Uint8List.fromList(
ttf.bytes.buffer.asUint8List(start, _wordAlign(len, 4)));
hhea.buffer
.asByteData()
.setUint16(34, glyphsInfo.length); // numOfLongHorMetrics
tables[TtfParser.hhea_table] = hhea;
tablesLength[TtfParser.hhea_table] = len;
}
{
// HMTX table
final int len = 4 * glyphsInfo.length;
final Uint8List hmtx = Uint8List(_wordAlign(len, 4));
final int hmtxOffset = ttf.tableOffsets[TtfParser.hmtx_table];
final ByteData hmtxData = hmtx.buffer.asByteData();
int index = 0;
for (TtfGlyphInfo glyph in glyphsInfo) {
hmtxData.setUint32(
index, ttf.bytes.getInt32(hmtxOffset + glyph.index * 4));
index += 4;
}
tables[TtfParser.hmtx_table] = hmtx;
tablesLength[TtfParser.hmtx_table] = len;
}
{
// CMAP table
final Uint8List cmap = Uint8List(_wordAlign(0x112, 4));
cmap.setAll(3, <int>[1, 0, 1, 0, 0, 0, 0, 0, 12, 0, 0, 1, 6]);
final ByteData cmapData = cmap.buffer.asByteData();
for (int i = 1; i < chars.length; i++) {
cmapData.setUint8(i + 18, i);
}
tables[TtfParser.cmap_table] = cmap;
tablesLength[TtfParser.cmap_table] = 0x112;
}
{
final List<int> bytes = <int>[];
final int numTables = tables.length;
// Create the file header
final ByteData start = ByteData(12 + numTables * 16);
start.setUint32(0, 0x00010000);
start.setUint16(4, numTables);
int pot = numTables;
while (pot & (pot - 1) != 0) {
pot++;
}
start.setUint16(6, pot * 16);
start.setUint16(8, math.log(pot).toInt());
start.setUint16(10, pot * 16 - numTables * 16);
// Create the table directory
int count = 0;
int offset = 12 + numTables * 16;
tables.forEach((String name, Uint8List data) {
final List<int> runes = name.runes.toList();
start.setUint8(12 + count * 16, runes[0]);
start.setUint8(12 + count * 16 + 1, runes[1]);
start.setUint8(12 + count * 16 + 2, runes[2]);
start.setUint8(12 + count * 16 + 3, runes[3]);
start.setUint32(12 + count * 16 + 4,
_calcTableChecksum(data.buffer.asByteData())); // checkSum
start.setUint32(12 + count * 16 + 8, offset); // offset
start.setUint32(12 + count * 16 + 12, tablesLength[name]); // length
offset += data.lengthInBytes;
count++;
});
bytes.addAll(start.buffer.asUint8List());
tables.forEach((String name, Uint8List data) {
bytes.addAll(data.buffer.asUint8List());
});
return Uint8List.fromList(bytes);
}
}
}
... ...
... ... @@ -21,37 +21,22 @@ class PdfTtfFont extends PdfFont {
PdfTtfFont(PdfDocument pdfDocument, ByteData bytes)
: font = TtfParser(bytes),
super._create(pdfDocument, subtype: '/TrueType') {
final PdfObjectStream file = PdfObjectStream(pdfDocument, isBinary: true);
final Uint8List data = bytes.buffer.asUint8List();
file.buf.putBytes(data);
file.params['/Length1'] = PdfStream.intNum(data.length);
_charMin = 32;
_charMax = 255;
final List<String> widths = <String>[];
for (int i = _charMin; i <= _charMax; i++) {
widths.add((glyphMetrics(i).advanceWidth * 1000.0).toInt().toString());
}
unicodeCMap = PdfObject(pdfDocument);
file = PdfObjectStream(pdfDocument, isBinary: true);
unicodeCMap = PdfUnicodeCmap(pdfDocument);
descriptor = PdfFontDescriptor(this, file);
widthsObject = PdfArrayObject(pdfDocument, widths);
widthsObject = PdfArrayObject(pdfDocument, <String>[]);
}
PdfObject unicodeCMap;
PdfUnicodeCmap unicodeCMap;
PdfFontDescriptor descriptor;
PdfObjectStream file;
PdfArrayObject widthsObject;
final TtfParser font;
int _charMin;
int _charMax;
@override
String get fontName => font.fontName;
... ... @@ -74,14 +59,80 @@ class PdfTtfFont extends PdfFont {
@override
void _prepare() {
int charMin;
int charMax;
super._prepare();
final TtfWriter ttfWriter = TtfWriter(font);
final Uint8List data = ttfWriter.withChars(unicodeCMap.cmap);
file.buf.putBytes(data);
file.params['/Length1'] = PdfStream.intNum(data.length);
params['/BaseFont'] = PdfStream.string('/' + fontName);
params['/FirstChar'] = PdfStream.intNum(_charMin);
params['/LastChar'] = PdfStream.intNum(_charMax);
params['/Widths'] = widthsObject.ref();
params['/FontDescriptor'] = descriptor.ref();
// params['/Encoding'] = PdfStream.string('/Identity-H');
// params['/ToUnicode'] = unicodeCMap.ref();
if (font.unicode) {
if (params.containsKey('/Encoding')) {
params.remove('/Encoding');
}
params['/ToUnicode'] = unicodeCMap.ref();
charMin = 0;
charMax = unicodeCMap.cmap.length - 1;
for (int i = charMin; i <= charMax; i++) {
widthsObject.values.add(
(glyphMetrics(unicodeCMap.cmap[i]).advanceWidth * 1000.0)
.toInt()
.toString());
}
} else {
charMin = 32;
charMax = 255;
for (int i = charMin; i <= charMax; i++) {
widthsObject.values
.add((glyphMetrics(i).advanceWidth * 1000.0).toInt().toString());
}
}
params['/FirstChar'] = PdfStream.intNum(charMin);
params['/LastChar'] = PdfStream.intNum(charMax);
params['/Widths'] = widthsObject.ref();
}
@override
PdfStream putText(String text) {
if (!font.unicode) {
return super.putText(text);
}
final Runes runes = text.runes;
final List<int> bytes = List<int>();
for (int rune in runes) {
int char = unicodeCMap.cmap.indexOf(rune);
if (char == -1) {
char = unicodeCMap.cmap.length;
unicodeCMap.cmap.add(rune);
}
bytes.add(char);
}
return PdfStream()
..putBytes(latin1.encode('('))
..putTextBytes(bytes)
..putBytes(latin1.encode(')'));
}
@override
PdfFontMetrics stringMetrics(String s) {
if (s.isEmpty || !font.unicode) {
return super.stringMetrics(s);
}
final Runes runes = s.runes;
final List<int> bytes = List<int>();
runes.forEach(bytes.add);
final Iterable<PdfFontMetrics> metrics = bytes.map(glyphMetrics);
return PdfFontMetrics.append(metrics);
}
}
... ...
/*
* Copyright (C) 2017, David PHAM-VAN <dev.nfet.net@gmail.com>
*
* 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.
*/
part of pdf;
class PdfUnicodeCmap extends PdfObjectStream {
PdfUnicodeCmap(PdfDocument pdfDocument) : super(pdfDocument);
final List<int> cmap = <int>[0];
@override
void _prepare() {
buf.putString('/CIDInit/ProcSet findresource begin\n'
'12 dict begin\n'
'begincmap\n'
'/CIDSystemInfo<<\n'
'/Registry (Adobe)\n'
'/Ordering (UCS)\n'
'/Supplement 0\n'
'>> def\n'
'/CMapName/Adobe-Identity-UCS def\n'
'/CMapType 2 def\n'
'1 begincodespacerange\n'
'<00> <FF>\n'
'endcodespacerange\n'
'${cmap.length} beginbfchar\n');
for (int key = 0; key < cmap.length; key++) {
final int value = cmap[key];
buf.putString('<' +
key.toRadixString(16).toUpperCase().padLeft(2, '0') +
'> <' +
value.toRadixString(16).toUpperCase().padLeft(4, '0') +
'>\n');
}
buf.putString('endbfchar\n'
'endcmap\n'
'CMapName currentdict /CMap defineresource pop\n'
'end\n'
'end');
super._prepare();
}
}
... ...
... ... @@ -65,10 +65,11 @@ void main() {
final PdfGraphics g = page.getGraphics();
int top = 0;
const String s = 'Hello ';
const String s = 'Hello Lukáča ';
printTextTtf(g, s, File('open-sans.ttf'), 30.0 + 30.0 * top++);
printTextTtf(g, s, File('roboto.ttf'), 30.0 + 30.0 * top++);
printTextTtf(g, s, File('noto-sans.ttf'), 30.0 + 30.0 * top++);
final File file = File('ttf.pdf');
file.writeAsBytesSync(pdf.save());
... ...