1
0
mirror of https://github.com/fadden/6502bench.git synced 2024-06-25 05:29:31 +00:00

Add visualization sets to exported HTML

We now generate GIF images for visualizations and add inline
references to them in the HTML output.

Images are scaled using the HTML img properties.  This works well
on some browsers, but others insist on "smooth" scaling that blurs
out the pixels.  This may require a workaround.

An extra blank line is now added above visualizations.  This helps
keep the image and data visually grouped.

The Apple II bitmap test project was updated to have a visualization
set with multiple images at the top of the file.
This commit is contained in:
Andy McFadden 2019-12-13 16:52:50 -08:00
parent 0b0944e0fc
commit 5d9b9753e8
13 changed files with 187 additions and 8 deletions

View File

@ -131,6 +131,7 @@ namespace SourceGen {
public const string EXPORT_TEXT_MODE = "export-text-mode";
public const string EXPORT_SELECTION_ONLY = "export-selection-only";
public const string EXPORT_LONG_LABEL_NEW_LINE = "export-long-label-new-line";
public const string EXPORT_GENERATE_IMAGE_FILES = "export-generate-image-files";
// Internal debugging features.
public const string DEBUG_MENU_ENABLED = "debug-menu-enabled";

View File

@ -19,6 +19,7 @@ using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Asm65;
using CommonUtil;
@ -43,6 +44,11 @@ namespace SourceGen {
/// </summary>
public bool IncludeNotes { get; set; }
/// <summary>
/// Should image files be generated?
/// </summary>
public bool GenerateImageFiles { get; set; }
/// <summary>
/// If set, labels that are wider than the label column should go on their own line.
/// </summary>
@ -83,6 +89,11 @@ namespace SourceGen {
/// </summary>
private Formatter mFormatter;
/// <summary>
/// Directory path for image files.
/// </summary>
private string mImageDirPath;
/// <summary>
/// The cumulative width of the columns determines the start point.
/// </summary>
@ -315,7 +326,7 @@ namespace SourceGen {
TextUtil.AppendPaddedString(sb, parts.Comment, mColStart[(int)Col.Label]);
break;
case LineListGen.Line.Type.VisualizationSet:
break; // TODO(xyzzy)
return; // show nothing
case LineListGen.Line.Type.Blank:
break;
default:
@ -371,8 +382,8 @@ namespace SourceGen {
/// <summary>
/// Generates HTML output to the specified path.
/// </summary>
/// <param name="pathName">Full pathname of output file. This defines the root
/// directory if there are additional files.</param>
/// <param name="pathName">Full pathname of output file (including ".html"). This
/// defines the root directory if there are additional files.</param>
/// <param name="overwriteCss">If set, existing CSS file will be replaced.</param>
public void OutputToHtml(string pathName, bool overwriteCss) {
string exportTemplate = RuntimeDataAccess.GetPathName(HTML_EXPORT_TEMPLATE);
@ -388,6 +399,41 @@ namespace SourceGen {
return;
}
// We should only need the _IMG directory if there are visualizations.
if (GenerateImageFiles && mProject.VisualizationSets.Count != 0) {
string imageDirName = Path.GetFileNameWithoutExtension(pathName) + "_IMG";
string imageDirPath = Path.Combine(Path.GetDirectoryName(pathName), imageDirName);
bool exists = false;
try {
FileAttributes attr = File.GetAttributes(imageDirPath);
if ((attr & FileAttributes.Directory) != FileAttributes.Directory) {
string msg = string.Format(Res.Strings.ERR_FILE_EXISTS_NOT_DIR_FMT,
imageDirPath);
MessageBox.Show(msg, Res.Strings.ERR_FILE_GENERIC_CAPTION,
MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
exists = true;
} catch (FileNotFoundException) {
} catch (DirectoryNotFoundException) {
}
if (!exists) {
try {
Directory.CreateDirectory(imageDirPath);
} catch (Exception ex) {
string msg = string.Format(Res.Strings.ERR_DIR_CREATE_FAILED_FMT,
imageDirPath, ex.Message);
MessageBox.Show(msg, Res.Strings.ERR_FILE_GENERIC_CAPTION,
MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
}
// All good.
mImageDirPath = imageDirPath;
}
// Perform some quick substitutions. This could be done more efficiently,
// but we're only doing this on the template file, which should be small.
tmplStr = tmplStr.Replace("$ProjectName$", mProject.DataFileName);
@ -620,7 +666,16 @@ namespace SourceGen {
parts.Comment.Length);
break;
case LineListGen.Line.Type.VisualizationSet:
break; // TODO(xyzzy)
if (!GenerateImageFiles) {
// generate nothing at all
return;
}
while (colPos < mColStart[(int)Col.Label]) {
sb.Append(' ');
colPos++;
}
OutputVisualizationSet(line.FileOffset, sb);
break;
case LineListGen.Line.Type.Blank:
break;
default:
@ -631,6 +686,73 @@ namespace SourceGen {
tw.WriteLine(sb);
}
/// <summary>
/// Generate one or more GIF image files, and generate references to them.
/// </summary>
/// <param name="offset">Visualization set file offset.</param>
/// <param name="sb">String builder for the HTML output.</param>
private void OutputVisualizationSet(int offset, StringBuilder sb) {
if (!mProject.VisualizationSets.TryGetValue(offset,
out VisualizationSet visSet)) {
sb.Append("Internal error - visualization set missing");
Debug.Assert(false);
return;
}
if (visSet.Count == 0) {
sb.Append("Internal error - empty visualization set");
Debug.Assert(false);
return;
}
string imageDirFileName = Path.GetFileName(mImageDirPath);
for (int index = 0; index < visSet.Count; index++) {
Visualization vis = visSet[index];
// Encode a GIF the same size as the original bitmap.
GifBitmapEncoder encoder = new GifBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(vis.CachedImage));
// Create new or replace existing image file.
string fileName = "vis" + offset.ToString("x6") + "_" + index.ToString("d2") +
".gif";
using (FileStream stream = new FileStream(Path.Combine(mImageDirPath, fileName),
FileMode.Create)) {
encoder.Save(stream);
}
// Create as thumbnail, preserving proportions. I'm assuming most images
// will be small enough that generating a separate thumbnail would be
// counter-productive. This seems to look best if the height is consistent
// across all visualization lines, but that can create some monsters (e.g.
// a bitmap that's 1 pixel high and 40 wide).
int dimMult = 64;
//double maxDim = Math.Max(vis.CachedImage.Width, vis.CachedImage.Height);
double maxDim = vis.CachedImage.Height;
if (vis.CachedImage.Width > vis.CachedImage.Height * 2) {
// Too proportionally wide, so use the width as the limit. Allow it to
// up to 2x the max width (which can't cause the thumb height to exceed
// the height limit).
maxDim = vis.CachedImage.Width;
dimMult *= 2;
}
int thumbWidth = (int)Math.Round(dimMult * (vis.CachedImage.Width / maxDim));
int thumbHeight = (int)Math.Round(dimMult * (vis.CachedImage.Height / maxDim));
Debug.WriteLine(vis.CachedImage.Width + "x" + vis.CachedImage.Height + " --> " +
thumbWidth + "x" + thumbHeight + " (" + maxDim + ")");
if (index != 0) {
sb.Append("&nbsp;");
}
sb.Append("<img class=\"vis\" alt=\"vis\" src=\"");
sb.Append(imageDirFileName);
sb.Append('/');
sb.Append(fileName);
sb.Append("\" width=\"" + thumbWidth + "\" height=\"" + thumbHeight + "\"/>");
}
}
/// <summary>
/// Appends a string to the string buffer. If the number of characters in the buffer
/// is less than the desired start position, spaces will be added. At least one space

View File

@ -957,6 +957,12 @@ namespace SourceGen {
mProject.GetAnattrib(offset - 1).IsData) {
// Transition from data to code. (Don't add blank line for inline data.)
lines.Add(GenerateBlankLine(offset));
} else if (mProject.VisualizationSets.ContainsKey(offset) && !addBlank &&
offset != 0) {
// Blank line before visualization set helps keep image visually grouped
// with its data. (Slightly weird things happen with .ORG at the start of
// the file; don't try to add a blank there.)
lines.Add(GenerateBlankLine(offset));
} else if (addBlank) {
// Previous instruction wanted to be followed by a blank line.
lines.Add(GenerateBlankLine(offset));

View File

@ -2205,6 +2205,7 @@ namespace SourceGen {
Exporter eport = new Exporter(mProject, CodeLineList, mOutputFormatter,
dlg.ColFlags, rightWidths);
eport.IncludeNotes = dlg.IncludeNotes;
eport.GenerateImageFiles = dlg.GenerateImageFiles;
eport.LongLabelNewLine = dlg.LongLabelNewLine;
if (dlg.SelectionOnly) {
DisplayListSelection selection = mMainWin.CodeDisplayList.SelectedIndices;

View File

@ -56,6 +56,7 @@ limitations under the License.
<system:String x:Key="str_ErrBadTypeHint">Type hint not recognized</system:String>
<system:String x:Key="str_ErrBadVisualizationFmt">Invalid visualization item: {0}</system:String>
<system:String x:Key="str_ErrBadVisualizationSetFmt">Invalid visualization set at +{0:x6}</system:String>
<system:String x:Key="str_ErrDirCreateFailedFmt">Unable to create directory {0}: {1}</system:String>
<system:String x:Key="str_ErrDuplicateLabelFmt">Removed duplicate label '{0}' (offset +{1:x6})</system:String>
<system:String x:Key="str_ErrFileCopyFailedFmt">Failed copying {0} to {1}: {2}.</system:String>
<system:String x:Key="str_ErrFileExistsNotDirFmt">The file {0} exists, but is not a directory.</system:String>

View File

@ -93,6 +93,8 @@ namespace SourceGen.Res {
(string)Application.Current.FindResource("str_ErrBadVisualizationFmt");
public static string ERR_BAD_VISUALIZATION_SET_FMT =
(string)Application.Current.FindResource("str_ErrBadVisualizationSetFmt");
public static string ERR_DIR_CREATE_FAILED_FMT =
(string)Application.Current.FindResource("str_ErrDirCreateFailedFmt");
public static string ERR_DUPLICATE_LABEL_FMT =
(string)Application.Current.FindResource("str_ErrDuplicateLabelFmt");
public static string ERR_FILE_COPY_FAILED_FMT =

View File

@ -323,7 +323,7 @@ assembler.</p>
<p>The text output is similar to what you'd get by copying lines to the
clipboard and pasting them into a text file, except that you have greater
control over which columns are included. The HTML version is augmented
with links.</p>
with links and (optionally) images.</p>
<p>Use File &gt; Export to open the export dialog. You have several
options:</p>
@ -346,6 +346,8 @@ options:</p>
plain text and Comma-Separated Value format. The latter is useful
for importing source code into another application, such as a
spreadsheet.</li>
<li><b>Generate image files</b>. When exporting to HTML, selecting this
will cause GIF images to be generated for visualizations.</li>
<li><b>Overwrite CSS file</b>. Some aspects of the HTML output's format
are defined by a file called "SGStyle.css", which may be shared between
multiple HTML files and customized. The file is copied out

View File

@ -23,4 +23,20 @@ a[href^="#"]:link, a[href^="#"]:visited {
a[href^="#"]:hover {
color: blue;
text-decoration: underline;
}
}
/*
* Put a border around visualizations. Disable smoothing so they don't
* blur out.
*
* Pixelated rendering works in Chrome, Safari, and IE11, but doesn't in
* current versions of Edge and Firefox. We could do it with Javascript
* Canvas (e.g. https://stackoverflow.com/a/19129822/294248), or by
* generating scaled thumbnail files directly.
*/
.vis {
border: 1px solid gray;
-ms-interpolation-mode: nearest-neighbor;
image-rendering: pixelated;
}

View File

@ -19,7 +19,10 @@
"Comments":{
},
"LongComments":{
},
"222":{
"Text":"\r\nPurple/green diagonal\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0},
"0":{
"Text":"Some images:","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}},
"Notes":{
"222":{
"Text":"Bitmaps here","BoxMode":false,"MaxWidth":80,"BackgroundColor":-256}},
@ -43,6 +46,19 @@
"LvTables":{
},
"VisualizationSets":{
"0":{
"Items":[{
"Tag":"multi1","VisGenIdent":"apple2-hi-res-bitmap","VisGenParams":{
"offset":222,"byteWidth":1,"height":8,"colStride":0,"rowStride":0,"isColor":true,"isFirstOdd":false}},
{
"Tag":"multi2","VisGenIdent":"apple2-hi-res-bitmap","VisGenParams":{
"offset":230,"byteWidth":1,"height":8,"colStride":0,"rowStride":0,"isColor":true,"isFirstOdd":false}},
{
"Tag":"multi3","VisGenIdent":"apple2-hi-res-bitmap","VisGenParams":{
"offset":238,"byteWidth":12,"height":1,"colStride":0,"rowStride":0,"isColor":false,"isFirstOdd":false}},
{
"Tag":"multi4","VisGenIdent":"apple2-hi-res-bitmap","VisGenParams":{
"offset":270,"byteWidth":3,"height":8,"colStride":0,"rowStride":0,"isColor":true,"isFirstOdd":false}}]},
"222":{
"Items":[{
"Tag":"vis0000de","VisGenIdent":"apple2-hi-res-bitmap","VisGenParams":{

View File

@ -79,6 +79,7 @@ namespace SourceGen.Sandbox {
/// Prepares the plugin directory. Creates it and copies PluginCommon.dll in.
/// Throws an exception if something fails.
/// </summary>
/// <exception cref="IOException">Various failures from file/dir operations.</exception>
public static void PreparePluginDir() {
string dstDir = GetPluginDirPath();
if (File.Exists(dstDir) && !Directory.Exists(dstDir)) {

View File

@ -47,7 +47,8 @@ namespace SourceGen {
public ReadOnlyDictionary<string, object> VisGenParams { get; private set; }
/// <summary>
/// Cached reference to 2D image, useful for thumbnails. Not serialized.
/// Cached reference to 2D image, useful for thumbnails. Not serialized. This always
/// has an image reference; in times of trouble it will point at BROKEN_IMAGE.
/// </summary>
/// <remarks>
/// Because the underlying data never changes, we only need to regenerate the

View File

@ -102,6 +102,7 @@ limitations under the License.
<GroupBox Grid.Row="2" Header="HTML Options" Padding="2,4">
<StackPanel>
<CheckBox Content="Generate image files" Margin="0,2,0,0" IsChecked="{Binding GenerateImageFiles}"/>
<CheckBox Content="Overwrite CSS file" Margin="0,2,0,0" IsChecked="{Binding OverwriteCss}"/>
</StackPanel>
</GroupBox>

View File

@ -165,6 +165,12 @@ namespace SourceGen.WpfGui {
set { mOverwriteCss = value; OnPropertyChanged(); }
}
private bool mGenerateImageFiles;
public bool GenerateImageFiles {
get { return mGenerateImageFiles; }
set { mGenerateImageFiles = value; OnPropertyChanged(); }
}
private enum TextMode {
Unknown = 0,
PlainText,
@ -210,6 +216,8 @@ namespace SourceGen.WpfGui {
SelectionOnly = AppSettings.Global.GetBool(AppSettings.EXPORT_SELECTION_ONLY, false);
LongLabelNewLine =
AppSettings.Global.GetBool(AppSettings.EXPORT_LONG_LABEL_NEW_LINE, false);
GenerateImageFiles =
AppSettings.Global.GetBool(AppSettings.EXPORT_GENERATE_IMAGE_FILES, true);
int[] colWidths = new int[] { 9, 8, 11, 72 }; // 100-col output
string colStr = AppSettings.Global.GetString(AppSettings.EXPORT_COL_WIDTHS, null);
@ -248,6 +256,7 @@ namespace SourceGen.WpfGui {
AppSettings.Global.SetBool(AppSettings.EXPORT_SHOW_ATTR, ShowAttr);
AppSettings.Global.SetBool(AppSettings.EXPORT_SELECTION_ONLY, SelectionOnly);
AppSettings.Global.SetBool(AppSettings.EXPORT_LONG_LABEL_NEW_LINE, LongLabelNewLine);
AppSettings.Global.SetBool(AppSettings.EXPORT_GENERATE_IMAGE_FILES,GenerateImageFiles);
int[] colWidths = new int[] {
AsmLabelColWidth, AsmOpcodeColWidth, AsmOperandColWidth, AsmCommentColWidth
};