Skip to content

Commit c16b902

Browse files
authored
Merge pull request #9 from nelinory/development
Development to Main
2 parents 1ec3b5b + ad52714 commit c16b902

31 files changed

+379
-61
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ SupernoteSharp is an unofficial library for Supernote paper-like tablet by Ratta
1212
This project is heavily inspired by https://github.com/jya-dev/supernote-tool.
1313

1414
### Supported file formats
15-
- `*.note` file created on Supernote A5X/A6X (firmware Chauvet 2.8.22)
16-
- `*.mark` pdf annotations created on Supernote A5X/A6X (firmware Chauvet 2.8.22)
15+
- `*.note` file created on Supernote A5X/A6X (firmware Chauvet 2.9.24)
16+
- `*.mark` pdf annotations created on Supernote A5X/A6X (firmware Chauvet 2.9.24)
1717

1818
### Key Features - A5X/A6X models only
1919
- Export `*.note`/`*.mark` file structure (metadata)

SupernoteSharp/A5X_Metadata_Schema.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,20 @@ header
1616
"FILE_ID"
1717
"FILE_RECOGN_TYPE"
1818
"FILE_RECOGN_LANGUAGE"
19+
"PDFSTYLE" - "none" means no PDF template is used
20+
"PDFSTYLEMD5" - 0 means no PDF template is used
21+
"STYLEUSAGETYPE" - 0 means normal style (IMAGE), 2 is PDF template as style (PDF)
1922
footer
2023
"PAGEXXXX" [..] - max pages 9999
2124
"TITLE_XXXX" [..]
2225
"KEYWORD_XXXX" [..]
26+
"COVER_X" - COVER_1 means there is cover, COVER_0 means no cover
27+
"DIRTY"
2328
"LINKO_XXXX" [..] - outbound links
2429
"LINKI_XXXX" [..] - inbound links
25-
"STYLE_XXXX" [..]
26-
"COVER_1" - COVER_1 means there is cover, COVER_0 means no cover
27-
"DIRTY"
2830
"FILE_FEATURE"
31+
"PDFSTYLELIST"
32+
"STYLE_XXXX" [..]
2933
keywords []
3034
"KEYWORDPAGE"
3135
"KEYWORDSEQNO"
@@ -72,6 +76,7 @@ pages
7276
"RECOGNTYPE"
7377
"RECOGNFILESTATUS"
7478
"RECOGNLANGUAGE"
79+
"EXTERNALLINKINFO"
7580
"FIVESTAR" []
7681
layers [] - max 5 layers supported
7782
"LAYERTYPE"

SupernoteSharp/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System.Reflection;
22

3-
[assembly: AssemblyVersion("0.6.*")]
3+
[assembly: AssemblyVersion("0.7.*")]
44
[assembly: AssemblyTitle("SupernoteSharp")]
55
[assembly: AssemblyProduct("SupernoteSharp")]
6-
[assembly: AssemblyCopyright("Copyright © nelinory 2023")]
6+
[assembly: AssemblyCopyright("Copyright © nelinory 2024")]

SupernoteSharp/Business/Converter.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -338,9 +338,12 @@ public byte[] ConvertAll(bool vectorize = false, bool enableLinks = false)
338338
private byte[] CreatePdf(Dictionary<int, VectSharp.Page> pdfPages, bool enableLinks)
339339
{
340340
// add links if requested
341-
Dictionary<string, string> links = null;
341+
Dictionary<string, string> links = new Dictionary<string, string>();
342342
if (enableLinks == true)
343-
links = AddLinks(pdfPages);
343+
{
344+
AddLinks(pdfPages, _notebook.Links, links);
345+
AddLinks(pdfPages, _notebook.TemplateLinks, links);
346+
}
344347

345348
// create the final pdf document
346349
Document pdfDocument = new Document();
@@ -353,11 +356,8 @@ private byte[] CreatePdf(Dictionary<int, VectSharp.Page> pdfPages, bool enableLi
353356
}
354357
}
355358

356-
private Dictionary<string, string> AddLinks(Dictionary<int, VectSharp.Page> pdfPages)
359+
private Dictionary<string, string> AddLinks(Dictionary<int, VectSharp.Page> pdfPages, List<Link> noteLinks, Dictionary<string, string> links)
357360
{
358-
List<Link> noteLinks = _notebook.Links;
359-
Dictionary<string, string> links = new Dictionary<string, string>();
360-
361361
foreach (KeyValuePair<int, VectSharp.Page> kvp in pdfPages)
362362
{
363363
// get all outbound links for the current page
@@ -369,7 +369,7 @@ private Dictionary<string, string> AddLinks(Dictionary<int, VectSharp.Page> pdfP
369369
List<Link> webLinks = outboundLinks.Where(x => x.Type == (int)LinkType.Web).ToList();
370370
foreach (Link webLink in webLinks)
371371
{
372-
string webLinkTag = $"WebLink_{webLink.Metadata["LINKBITMAP"]}";
372+
string webLinkTag = $"WebLink_{webLink.Bitmap}";
373373
string webLinkUrl = Encoding.UTF8.GetString(System.Convert.FromBase64String(webLink.FilePath));
374374

375375
kvp.Value.Graphics.StrokeRectangle(webLink.Rect.left, webLink.Rect.top, webLink.Rect.width, webLink.Rect.height,
@@ -392,8 +392,8 @@ private Dictionary<string, string> AddLinks(Dictionary<int, VectSharp.Page> pdfP
392392
// each internal link is a pair of outbound and inbound, they have the same timestamp and rect coordinates
393393
Link targetLink = noteLinks.Where(x => x.InOut == (int)LinkDirection.In && x.Timestamp == sourceLink.Timestamp && x.Rect.Equals(sourceLink.Rect)).FirstOrDefault();
394394

395-
string sourceLinkTag = $"SourceLink_{sourceLink.Metadata["LINKBITMAP"]}";
396-
string targetLinkTag = $"TargetLink_{targetLink.Metadata["LINKBITMAP"]}";
395+
string sourceLinkTag = $"SourceLink_{sourceLink.Bitmap}";
396+
string targetLinkTag = $"TargetLink_{targetLink.Bitmap}";
397397

398398
pdfPages[sourceLink.PageNumber].Graphics.StrokeRectangle(sourceLink.Rect.left, sourceLink.Rect.top, sourceLink.Rect.width, sourceLink.Rect.height,
399399
Colour.FromRgba(0, 0, 0, 0), tag: sourceLinkTag);

SupernoteSharp/Business/Decoder.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,14 +146,12 @@ internal class RattaRleDecoder : IBaseDecoder
146146
Queue<(byte, int)> waiting = new Queue<(byte, int)>();
147147
while (true)
148148
{
149-
byte colorcode = bin.MoveNext() ? bin.Current : (byte)0;
150-
int length = bin.MoveNext() ? bin.Current : 0;
149+
bin.MoveNext();
150+
byte colorcode = bin.Current;
151+
bin.MoveNext();
152+
int length = bin.Current;
151153
bool dataPushed = false;
152154

153-
// we reach the end of the enumeration due to colorcode being 0
154-
if (colorcode == 0)
155-
break;
156-
157155
if (holder.Item2 > 0)
158156
{
159157
(byte prevColorcode, int prevLength) = holder;

SupernoteSharp/Business/Parser.cs

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Collections.Generic;
55
using System.IO;
66
using System.Linq;
7+
using System.Text;
78

89
namespace SupernoteSharp.Business
910
{
@@ -104,6 +105,25 @@ public Notebook LoadNotebook(FileStream fileStream, Policy policy)
104105
int recognTextAddress = metadata.Pages[i].ContainsKey("RECOGNTEXT") == true ? Int32.Parse((string)metadata.Pages[i]["RECOGNTEXT"]) : 0;
105106
if (recognTextAddress > 0)
106107
notebook.Pages[i].RecognText = GetContentAtAddress(fileStream, recognTextAddress);
108+
109+
// attach external link info
110+
int externalLinkInfoAddress = metadata.Pages[i].ContainsKey("EXTERNALLINKINFO") == true ? Int32.Parse((string)metadata.Pages[i]["EXTERNALLINKINFO"]) : 0;
111+
if (externalLinkInfoAddress > 0)
112+
notebook.Pages[i].ExternalLinkInfo = GetContentAtAddress(fileStream, externalLinkInfoAddress);
113+
}
114+
115+
// attach template link data
116+
List<Page> pagesWithTemplateLinks = notebook.Pages.Where(x => x.ExternalLinkInfo != null).ToList();
117+
foreach (Page page in pagesWithTemplateLinks)
118+
{
119+
// ExternalLinkInfo contains multiple links separated by a '|' character
120+
string[] links = Encoding.UTF8.GetString(page.ExternalLinkInfo).Split("|", StringSplitOptions.RemoveEmptyEntries);
121+
foreach (string link in links)
122+
{
123+
// each link properties are separated by a ',' character
124+
string[] linkProperties = link.Split(",", StringSplitOptions.RemoveEmptyEntries);
125+
notebook.TemplateLinks.AddRange(GetTemplateLink(linkProperties, notebook.FileId));
126+
}
107127
}
108128

109129
return notebook;
@@ -142,20 +162,11 @@ private List<int> GetPageNumberFromFooterProperty(Dictionary<string, object> foo
142162
IEnumerable<string> propertyKeys = footer.Keys.Where(p => p.StartsWith(propertyPrefix));
143163
foreach (string property in propertyKeys)
144164
{
145-
if (footer[property].GetType() != typeof(string))
146-
{
147-
// TODO: Need Supernote A5 test note
148-
/*
149-
if type(footer[k]) == list:
150-
for _ in range(len(footer[k])):
151-
page_numbers.append(int(k[6:10]) - 1)
152-
else:
153-
page_numbers.append(int(k[6:10]) - 1) # e.g. get '0123' from 'TITLE_01234567'
154-
*/
155-
throw new NotImplementedException();
156-
}
165+
// get '0123' from 'TITLE_01234567' or 'LINKI_01234567' or 'LINKO_01234567'
166+
if (footer[property] is List<string> itemList)
167+
pageNumbers.AddRange(itemList.Select(p => Convert.ToInt32(property.Substring(6, 4))));
157168
else
158-
pageNumbers.Add(Int32.Parse(property.Substring(6, 4))); // get '0123' from 'TITLE_01234567'
169+
pageNumbers.Add(Int32.Parse(property.Substring(6, 4)));
159170
}
160171

161172
return pageNumbers;
@@ -182,5 +193,47 @@ private List<int> GetBitmapAddress(Metadata metadata, int pageNumber)
182193

183194
return bitmapAddresses;
184195
}
196+
197+
private List<Link> GetTemplateLink(string[] templateLink, string fileId)
198+
{
199+
List<Link> templateLinks = new List<Link>();
200+
201+
Dictionary<string, object> metadata = new Dictionary<string, object>
202+
{
203+
["LINKRECT"] = $"{Encoding.UTF8.GetString(Convert.FromBase64String(templateLink[3]))}," +
204+
$"{Encoding.UTF8.GetString(Convert.FromBase64String(templateLink[4]))}," +
205+
$"{Encoding.UTF8.GetString(Convert.FromBase64String(templateLink[5]))}," +
206+
$"{Encoding.UTF8.GetString(Convert.FromBase64String(templateLink[6]))}",
207+
["LINKTYPE"] = Int32.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(templateLink[7]))) == 0 ? ((Int32)LinkType.Page).ToString() : ((Int32)LinkType.Web).ToString(),
208+
["LINKINOUT"] = ((Int32)LinkDirection.Out).ToString(),
209+
["LINKBITMAP"] = DateTime.Now.Ticks.ToString(),
210+
["LINKTIMESTAMP"] = DateTime.Now.Ticks.ToString(),
211+
["LINKFILE"] = templateLink[8],
212+
["LINKFILEID"] = fileId,
213+
["PAGEID"] = Encoding.UTF8.GetString(Convert.FromBase64String(templateLink[1]))
214+
};
215+
216+
Link linkOut = new Link(metadata)
217+
{
218+
PageNumber = Int32.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(templateLink[0]))) - 1 // link indexes are not 0 based
219+
};
220+
221+
templateLinks.Add(linkOut);
222+
223+
// in case we are building template page links, we need to ensure we have both IN and OUT links
224+
// they are the same structure, with the exception of the LINKINOUT attribute
225+
if (linkOut.Type == (Int32)LinkType.Page)
226+
{
227+
metadata["LINKINOUT"] = ((Int32)LinkDirection.In).ToString();
228+
Link linkIn = new Link(metadata)
229+
{
230+
PageNumber = Int32.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(templateLink[2]))) - 1 // link indexes are not 0 based
231+
};
232+
233+
templateLinks.Add(linkIn);
234+
}
235+
236+
return templateLinks;
237+
}
185238
}
186239
}

SupernoteSharp/Business/SupernoteXParser.cs.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ internal override List<String> SN_SIGNATURES
3030
"noteSN_FILE_VER_20210009", // Firmware version C.291
3131
"noteSN_FILE_VER_20210010", // Firmware version Chauvet 2.1.6
3232
"noteSN_FILE_VER_20220011", // Firmware version Chauvet 2.5.17
33-
"noteSN_FILE_VER_20220013", // Firmware version Chauvet 2.6.19
34-
"markSN_FILE_VER_20220013" // Firmware version Chauvet 2.8.22
33+
"noteSN_FILE_VER_20220013", // Firmware version Chauvet 2.9.24
34+
"markSN_FILE_VER_20220013" // Firmware version Chauvet 2.9.24
3535
};
3636
}
3737
}
@@ -123,7 +123,7 @@ private static List<int> GetItemAddresses(Dictionary<string, object> footer, str
123123

124124
foreach (string itemKey in itemKeys)
125125
{
126-
if (footer[itemKey] is List<object> itemList)
126+
if (footer[itemKey] is List<string> itemList)
127127
itemAddresses.AddRange(itemList.Select(p => Convert.ToInt32(p)));
128128
else
129129
itemAddresses.Add(Convert.ToInt32(footer[itemKey]));

SupernoteSharp/Common/Enums.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ public enum VisibilityOverlay : byte { Default, Visible, Invisible }
1212
public enum LinkDirection { Out = 0, In = 1 }
1313

1414
public enum LinkType { Page = 0, File = 1, Web = 4 }
15+
16+
public enum StyleUsageType { Normal = 0, Pdf = 2 }
1517
}

SupernoteSharp/Entities/Link.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public class Link
1111
public int Position { get; private set; }
1212
public int Type { get; private set; }
1313
public int InOut { get; private set; }
14+
public string Bitmap { get; private set; }
1415
public (int left, int top, int width, int height) Rect { get; private set; }
1516
public string Timestamp { get; private set; }
1617
public string FilePath { get; private set; }
@@ -25,6 +26,7 @@ public Link(Dictionary<string, object> metadata)
2526
Position = Int32.Parse(((string)metadata["LINKRECT"]).Split(',')[1]); // get top value from "left,top,width,height"
2627
Type = Int32.Parse((string)metadata["LINKTYPE"]);
2728
InOut = Int32.Parse((string)metadata["LINKINOUT"]);
29+
Bitmap = (string)metadata["LINKBITMAP"];
2830

2931
// rectangle
3032
string[] rectangle = ((string)metadata["LINKRECT"]).Split(',');

SupernoteSharp/Entities/Notebook.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,15 @@ public class Notebook
1212
public List<Keyword> Keywords { get; private set; }
1313
public List<Title> Titles { get; private set; }
1414
public List<Link> Links { get; private set; }
15+
public List<Link> TemplateLinks { get; private set; }
1516
public List<Page> Pages { get; private set; }
1617
public string FileType { get; private set; }
1718
public string FileId { get; private set; }
1819
public bool IsRealtimeRecognition { get; private set; }
1920
public int TotalPages { get { return Pages.Count; } }
21+
public string PdfStyle { get; private set; }
22+
public string PdfStyleMd5 { get; private set; }
23+
public StyleUsageType StyleUsageType { get; private set; }
2024

2125
public Notebook(Metadata metadata)
2226
{
@@ -54,6 +58,8 @@ public Notebook(Metadata metadata)
5458
}
5559
}
5660

61+
TemplateLinks = new List<Link>();
62+
5763
Pages = new List<Page>();
5864
for (int i = 0; i < metadata.TotalPages; i++)
5965
{
@@ -63,6 +69,11 @@ public Notebook(Metadata metadata)
6369
FileType = (string)metadata.Header["FILE_TYPE"];
6470
FileId = (string)metadata.Header["FILE_ID"];
6571
IsRealtimeRecognition = (string)Metadata.Header["FILE_RECOGN_TYPE"] == "1";
72+
73+
// pdf note templates
74+
PdfStyle = metadata.Header.ContainsKey("PDFSTYLE") == true ? (string)metadata.Header["PDFSTYLE"] : "none";
75+
PdfStyleMd5 = metadata.Header.ContainsKey("PDFSTYLEMD5") == true ? (string)metadata.Header["PDFSTYLEMD5"] : "0";
76+
StyleUsageType = metadata.Header.ContainsKey("STYLEUSAGETYPE") == true ? Enum.Parse<StyleUsageType>(metadata.Header["STYLEUSAGETYPE"].ToString()) : StyleUsageType.Normal; ;
6677
}
6778

6879
public Page Page(int pageNumber)

0 commit comments

Comments
 (0)